﻿///<copyright>
/// Copyright © 2009 Stephen Styrchak. All Rights Reserved.
///</copyright>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;

namespace Ferpect.ComponentModel.Design
{
    [ToolboxItem(false)]
    internal class ScreenManagerControl : FlowLayoutPanel, ICustomTypeDescriptor
    {
        private readonly string componentTypeName = typeof(ScreenManager).FullName;
        private ScreenManager componentInstance;
        private Attribute[] replacementAttributes;
        private Control parent;

        public ScreenManagerControl()
        {
            //this.AutoScroll = true;
            this.AutoSize = true;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.parent != null)
                {
                    this.parent.SizeChanged -= this.OnParentSizeChanged;
                }
            }
            base.Dispose(disposing);
        }

        public ScreenManager Component
        {
            get
            {
                if (this.componentInstance == null)
                {
                    this.componentInstance = new ScreenManager();
                }
                return this.componentInstance;
            }
        }

        /// <summary>
        /// This method will remove or replace a number of the TComponent instance
        /// properties with properties from the TControl instance.  We really only
        /// care about doing this with extender properties.
        /// 
        /// For example, Name should refer to the root component class name, but the
        /// Name property is provided by an extender.  Keep in mind that there is
        /// a Name property on Control, but it is non-browsable.
        /// 
        /// GenerateMember and Modifiers do not apply to root components, but the
        /// TComponent instance is never a root component in this container.  Here,
        /// the extenders are removed if the TControl is a root component, or else
        /// the properties from TControl are used.
        /// </summary>
        private PropertyDescriptorCollection FilterComponentProperties(PropertyDescriptorCollection componentProperties, PropertyDescriptorCollection myProperties)
        {
            string[] filterNames = new string[] {
                "Name",
                "GenerateMember",
                "Modifiers",
            };

            Dictionary<string, PropertyDescriptor> filteredTable = new Dictionary<string, PropertyDescriptor>();
            Dictionary<string, PropertyDescriptor> lookupTable = new Dictionary<string, PropertyDescriptor>();

            foreach (PropertyDescriptor property in componentProperties)
            {
                filteredTable[property.Name] = property;
            }
            foreach (PropertyDescriptor property in myProperties)
            {
                if (property.IsBrowsable && property.Attributes[typeof(ExtenderProvidedPropertyAttribute)] != null)
                {
                    lookupTable[property.Name] = property;
                }
            }

            foreach (string filter in filterNames)
            {
                PropertyDescriptor myProperty = null;
                if (lookupTable.TryGetValue(filter, out myProperty))
                {
                    filteredTable[filter] = myProperty;
                }
                else if (filteredTable.ContainsKey(filter))
                {
                    filteredTable.Remove(filter);
                }
            }

            // Create the return value from the remaining properties
            int size = filteredTable.Count;
            PropertyDescriptor[] remainingProps = new PropertyDescriptor[size];
            filteredTable.Values.CopyTo(remainingProps, 0);

            return new PropertyDescriptorCollection(remainingProps);
        }

        private void OnTypeRefreshed(RefreshEventArgs args)
        {
            if (args != null && (Object.ReferenceEquals(this.componentInstance, args.ComponentChanged) || typeof(ScreenManager) == args.TypeChanged))
            {
                this.replacementAttributes = null;
                TypeDescriptor.Refreshed -= this.OnTypeRefreshed;
            }
        }

        #region ICustomTypeDescriptor Members

        /// <summary>
        /// This method is implemented a bit strangely because AttributeCollection does
        /// not support Add or Remove.  One does not appear on the collection, while the
        /// other just throws NotSupportedException.  So we have to instead build an
        /// array that we pass to an AttributeCollection constructor.
        /// </summary>
        public AttributeCollection GetAttributes()
        {
            AttributeCollection baseAttributes = TypeDescriptor.GetAttributes(this.Component);

            // Create a list of attributes with certain attribute types removed.
            List<Attribute> proxyAttributeList = new List<Attribute>();
            List<ViewDocumentDesignerAttribute> rootViewDesigners = new List<ViewDocumentDesignerAttribute>();
            List<ViewControlDesignerAttribute> controlViewDesigners = new List<ViewControlDesignerAttribute>();

            foreach (Attribute a in baseAttributes)
            {
                if (a is ViewDocumentDesignerAttribute)
                {
                    rootViewDesigners.Add((ViewDocumentDesignerAttribute)a);
                }
                else if (a is ViewControlDesignerAttribute)
                {
                    controlViewDesigners.Add((ViewControlDesignerAttribute)a);
                }
                else if (!(a is DesignerAttribute || a is DesignTimeVisibleAttribute))
                {
                    proxyAttributeList.Add(a);
                }
            }

            // Create a new AttributeCollection with the remaining attributes plus some replacements
            // for the ones we removed.
            Attribute[] replacements = this.GetReplacementAttributes(rootViewDesigners, controlViewDesigners);
            int numProxied = proxyAttributeList.Count;
            int numReplacements = replacements.Length;

            Attribute[] attributes = new Attribute[numProxied + numReplacements];
            proxyAttributeList.CopyTo(attributes);
            replacements.CopyTo(attributes, numProxied);

            return new AttributeCollection(attributes);
        }

        private Attribute[] GetReplacementAttributes(
            IList<ViewDocumentDesignerAttribute> rootViewDesigners,
            IList<ViewControlDesignerAttribute> controlViewDesigners)
        {
            if (this.replacementAttributes == null)
            {
                int rootDesignerCount = rootViewDesigners.Count;
                if (rootDesignerCount == 0)
                {
                    ++rootDesignerCount;
                    rootViewDesigners.Add(new ViewDocumentDesignerAttribute(typeof(ScreenManagerDesigner)));
                }

                int controlDesignerCount = controlViewDesigners.Count;
                if (controlDesignerCount == 0)
                {
                    ++controlDesignerCount;
                    controlViewDesigners.Add(new ViewControlDesignerAttribute(typeof(ViewControlDesigner<ScreenManager>)));
                }

                int total = rootDesignerCount + controlDesignerCount;
                this.replacementAttributes = new Attribute[total];
                int i = 0;
                foreach (ViewDocumentDesignerAttribute designer in rootViewDesigners)
                {
                    this.replacementAttributes[i++] = new DesignerAttribute(designer.DesignerTypeName, designer.BaseDesignerTypeName);
                }
                foreach (ViewControlDesignerAttribute designer in controlViewDesigners)
                {
                    this.replacementAttributes[i++] = new DesignerAttribute(designer.DesignerTypeName, designer.BaseDesignerTypeName);
                }

                //NOTE: caching should never be done in a custom type descriptor unless
                //      the cache is cleared when TypeDescriptor signals a type or instance
                //      refresh.
                TypeDescriptor.Refreshed += this.OnTypeRefreshed;
            }
            return this.replacementAttributes;
        }

        public string GetClassName()
        {
            return this.componentTypeName;
        }

        public string GetComponentName()
        {
            return this.Site.Name;
        }

        public TypeConverter GetConverter()
        {
            return TypeDescriptor.GetConverter(this.Component);
        }

        public EventDescriptor GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(this.Component);
        }

        public PropertyDescriptor GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty(this.Component);
        }

        public object GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(this.Component, editorBaseType);
        }

        public EventDescriptorCollection GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(this.Component, attributes);
        }

        public EventDescriptorCollection GetEvents()
        {
            return TypeDescriptor.GetEvents(this.Component);
        }

        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            PropertyDescriptorCollection componentProperties = this.WrapPropertyDescriptors(TypeDescriptor.GetProperties(this.Component, attributes), this.Component);
            PropertyDescriptorCollection myProperties = TypeDescriptor.GetProperties(this, attributes, true);
            PropertyDescriptorCollection filteredProperties = this.FilterComponentProperties(componentProperties, myProperties);

            return filteredProperties;
        }

        public PropertyDescriptorCollection GetProperties()
        {
            PropertyDescriptorCollection componentProperties = this.WrapPropertyDescriptors(TypeDescriptor.GetProperties(this.Component), this.Component);
            PropertyDescriptorCollection myProperties = TypeDescriptor.GetProperties(this, true);
            PropertyDescriptorCollection filteredProperties = this.FilterComponentProperties(componentProperties, myProperties);

            return filteredProperties;
        }

        public object GetPropertyOwner(PropertyDescriptor pd)
        {
            OwnerPropertyDescriptor ownedPropertyDescriptor = pd as OwnerPropertyDescriptor;
            if (ownedPropertyDescriptor != null)
            {
                return ownedPropertyDescriptor.Owner;
            }
            return this;
        }

        private PropertyDescriptorCollection WrapPropertyDescriptors(PropertyDescriptorCollection properties, object propertyOwner)
        {
            PropertyDescriptorCollection wrappedProperties = new PropertyDescriptorCollection(null);
            foreach (PropertyDescriptor property in properties)
            {
                wrappedProperties.Add(new OwnerPropertyDescriptor(property, propertyOwner, this));
            }
            return wrappedProperties;
        }

        #endregion

        protected override void OnClick(EventArgs e)
        {
            base.OnClick(e);
        }

        protected override void OnMouseClick(MouseEventArgs e)
        {
            base.OnMouseClick(e);
        }

        protected override void OnParentChanged(EventArgs e)
        {
            if (this.parent != null)
            {
                this.parent.SizeChanged -= this.OnParentSizeChanged;
            }
            if (this.Parent != null)
            {
                this.parent = this.Parent;
                this.parent.SizeChanged += this.OnParentSizeChanged;
                this.Height = this.parent.Height - this.parent.Padding.Vertical;
                this.Width = this.parent.Width - this.parent.Padding.Horizontal;
            }
            base.OnParentChanged(e);
        }

        private void OnParentSizeChanged(object sender, EventArgs args)
        {
            this.Width = this.parent.Width - this.Left * 2;
            this.MaximumSize = new Size(this.parent.Width - this.Left * 2, 0);
        }
    }
}
